package com.xique.park.service.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.xique.common.core.domain.R;
import com.xique.common.core.enums.PayStatus;
import com.xique.common.core.utils.DateUtils;
import com.xique.common.core.utils.SecurityUtils;
import com.xique.common.security.service.TokenService;
import com.xique.park.bean.domain.ParkInfo;
import com.xique.park.bean.domain.VerifyRecords;
import com.xique.park.bean.dto.ParkDeviceStatisticsDto;
import com.xique.park.bean.dto.PayOrderDto;
import com.xique.park.bean.vo.web.index.IncomeAnalysisVo;
import com.xique.park.bean.vo.web.index.IndexVo;
import com.xique.park.bean.vo.web.index.TypeAnalysisVo;
import com.xique.park.bean.vo.web.index.PassengerAnalysisVo;
import com.xique.park.service.service.*;
import com.xique.park.utils.RedisUtil;
import com.xique.system.api.RemoteSystemDeptService;
import com.xique.system.api.dto.SystemDeptDTO;
import com.xique.system.api.model.LoginSystemUser;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

/**
 * web 首页统计服务实现
 *
 * @author WLD
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class IndexServiceImpl implements IndexService {

    private final TokenService tokenService;

    private final IPayOrderService payOrderService;

    private final IParkInService parkInService;

    private final IParkOutService parkOutService;

    private final IParkInfoService parkInfoService;

    private final IVerifyRecordsService verifyRecordsService;

    private final IAccessDeviceService accessDeviceService;

    private final IParkDeviceNewService parkDeviceNewService;

    private final RemoteSystemDeptService remoteSystemDeptService;

    private final RedisUtil redisUtil;

    private static final String CACHE_KEY_DAY = "index:statistics:{}:day";

    private static final String CACHE_KEY_WEEK = "index:statistics:{}:week";

    private static final String CACHE_KEY_MONTH = "index:statistics:{}:month";

    private static final String LOGIN_USER_GET_ERROR = "登录用户获取失败";

    private static final String DAY_TYPE_ERROR = "查询日期类型错误";

    /**
     * 已支付状态列表
     */
    private static final List<String> PAID_LIST = Arrays.asList(PayStatus.PAID.getStatus(), PayStatus.CASH_PAID.getStatus());

    private static final BigDecimal HUNDRED = new BigDecimal("100");

    @Override
    public IndexVo statistics(Integer dayType) {
        // 获取登录用户信息
        LoginSystemUser loginSystemUser = getLoginSysUser();
        // 统计
        return doStatistics(loginSystemUser, dayType);
    }

    private IndexVo doStatistics(LoginSystemUser loginSystemUser, Integer dayType) {
        Long deptId = loginSystemUser.getSystemUser().getDeptId();
        Long parkId = loginSystemUser.getParkId();
        String userType = loginSystemUser.getUserType();
        Long projectId = SecurityUtils.getParkId();

        Day day;

        IndexVo indexVo;
        switch (userType) {
            // 系统管理员
            case "00":
                // 统计所有项目 - 统计昨日
                if (null == projectId) {
                    indexVo = statisticsByParkList(parkInfoService.selectAll(), dayType);
                    indexVo.setIsToday(false);
                } else {
                    // 统计单个项目 - 实时统计
                    day = handleDay(dayType, true);
                    indexVo = statisticsByPark(projectId, day.getStarDate(), day.getEndDate(), day.getLastStartDate(),  day.getLastEndDate());
                    indexVo.setIsToday(true);
                }
                return indexVo;
            // 服务商
            case "01":
                if (null == projectId) {
                    // 根据 dept_id 统计名下所有的项目 - 统计昨日
                    indexVo = statisticsByParkIdList(findDeptList(deptId), dayType);
                    indexVo.setIsToday(false);
                } else {
                    // 统计单个项目 - 实时统计
                    day = handleDay(dayType, true);
                    indexVo = statisticsByPark(projectId, day.getStarDate(), day.getEndDate(), day.getLastStartDate(), day.getLastEndDate());
                    indexVo.setIsToday(true);
                }
                return indexVo;
            // 项目管理员 - 实时统计
            case "02":
                // 计算统计日期
                day = handleDay(dayType, true);
                indexVo = statisticsByPark(parkId, day.getStarDate(), day.getEndDate(), day.getLastStartDate(), day.getLastEndDate());
                indexVo.setIsToday(true);
                return indexVo;
            default:
                throw new IllegalArgumentException(LOGIN_USER_GET_ERROR);
        }
    }

    private IndexVo statisticsByParkList(List<ParkInfo> parkInfoList, Integer dayTyp) {
        List<Long> parkIdList = parkInfoList.stream().map(ParkInfo::getId).collect(Collectors.toList());
        return statisticsByParkIdList(parkIdList, dayTyp);
    }

    private IndexVo statisticsByParkIdList(List<Long> parkIdList, Integer dayType) {
        Set<String> keys = new HashSet<>();
        parkIdList.forEach(parkId -> {
            String key;
            switch (dayType) {
                // 从缓存中获取昨日统计数据
                case 1:
                    key = StrUtil.format(CACHE_KEY_DAY, parkId);
                    break;
                // 从缓存中获取本周统计数据
                case 2:
                    key = StrUtil.format(CACHE_KEY_WEEK, parkId);
                    break;
                // 从缓存中获取本月统计数据
                case 3:
                    key = StrUtil.format(CACHE_KEY_MONTH, parkId);
                    break;
                default:
                    throw new IllegalArgumentException(DAY_TYPE_ERROR);
            }
            keys.add(key);
        });

        List<Object> indexVoList = redisUtil.multiGet(keys);

        // 处理结果
        return handleIndexList(indexVoList);
    }

    private IndexVo handleIndexList(List<Object> indexVoList) {
        long totalRevenue = 0;
        long temporaryRevenue = 0;
        long monthlyCardRevenue = 0;
        long businessRevenue = 0;
        long inCarRevenue = 0;
        long outCarRevenue = 0;
        double totalRingThan = 0;
        double temporaryRingThan = 0;
        double monthlyCardRingThan = 0;
        double businessRingThan = 0;
        double inCarRingThan = 0;
        double outCarRingThan = 0;
        long userCurrent = 0;
        long visitorCurrent = 0;
        int parkEquipmentOnline = 0;
        int parkEquipmentOffline = 0;
        int accessControlOnline = 0;
        int accessControlOffline = 0;
        Map<String, IncomeAnalysisVo> incomeAnalysisMap = new LinkedHashMap<>(12);
        Map<String, TypeAnalysisVo> typeAnalysisMap = new LinkedHashMap<>(12);
        Map<String, PassengerAnalysisVo> passengerAnalysisMap = new LinkedHashMap<>(12);

        for (Object obj : indexVoList) {
            IndexVo indexVo = (IndexVo) obj;
            if (indexVo != null) {
                // 基本数据封装
                totalRevenue = totalRevenue + indexVo.getTotalRevenue();
                temporaryRevenue = temporaryRevenue + indexVo.getTemporaryRevenue();
                monthlyCardRevenue = monthlyCardRevenue + indexVo.getMonthlyCardRevenue();
                businessRevenue = businessRevenue + indexVo.getBusinessRevenue();
                inCarRevenue = inCarRevenue + indexVo.getInCarRevenue();
                outCarRevenue = outCarRevenue + indexVo.getOutCarRevenue();
                totalRingThan = totalRingThan + indexVo.getTotalRingThan();
                temporaryRingThan = temporaryRingThan + indexVo.getTemporaryRingThan();
                monthlyCardRingThan = monthlyCardRingThan + indexVo.getMonthlyCardRingThan();
                businessRingThan = businessRingThan + indexVo.getBusinessRingThan();
                inCarRingThan = inCarRingThan + indexVo.getInCarRingThan();
                outCarRingThan = outCarRingThan + indexVo.getOutCarRingThan();
                userCurrent = userCurrent + indexVo.getUserCurrent();
                visitorCurrent = visitorCurrent + indexVo.getVisitorCurrent();
                parkEquipmentOnline = parkEquipmentOnline + indexVo.getParkEquipmentOnline();
                parkEquipmentOffline = parkEquipmentOffline + indexVo.getParkEquipmentOffline();
                accessControlOnline = accessControlOnline + indexVo.getAccessControlOnline();
                accessControlOffline = accessControlOffline + indexVo.getAccessControlOffline();

                // 收入分析
                List<IncomeAnalysisVo> incomeAnalysisVoList = indexVo.getIncomeAnalysisVoList();
                handleIncomeAnalysisList(incomeAnalysisVoList, incomeAnalysisMap);

                // 支付类型分析
                List<TypeAnalysisVo> typeAnalysisVoList = indexVo.getTypeAnalysisVoList();
                handleTypeAnalysisList(typeAnalysisVoList, typeAnalysisMap);

                // 人流量分析
                List<PassengerAnalysisVo> passengerAnalysisVoList = indexVo.getPassengerAnalysisVoList();
                handlePassengerAnalysisList(passengerAnalysisVoList, passengerAnalysisMap);
            }
        }

        BigDecimal size = BigDecimal.valueOf(indexVoList.size());

        // 组装返回对象
        return IndexVo.builder()
                .totalRevenue(totalRevenue)
                .temporaryRevenue(temporaryRevenue)
                .monthlyCardRevenue(monthlyCardRevenue)
                .businessRevenue(businessRevenue)
                .inCarRevenue(inCarRevenue)
                .outCarRevenue(outCarRevenue)
                .totalRingThan(BigDecimal.valueOf(totalRevenue).divide(size, 2, RoundingMode.UP).doubleValue())
                .temporaryRingThan(BigDecimal.valueOf(temporaryRingThan).divide(size, 2, RoundingMode.UP).doubleValue())
                .monthlyCardRingThan(BigDecimal.valueOf(monthlyCardRingThan).divide(size, 2, RoundingMode.UP).doubleValue())
                .businessRingThan(BigDecimal.valueOf(businessRingThan).divide(size, 2, RoundingMode.UP).doubleValue())
                .inCarRingThan(BigDecimal.valueOf(inCarRingThan).divide(size, 2, RoundingMode.UP).doubleValue())
                .outCarRingThan(BigDecimal.valueOf(outCarRingThan).divide(size, 2, RoundingMode.UP).doubleValue())
                .userCurrent(userCurrent)
                .visitorCurrent(visitorCurrent)
                .parkEquipmentOnline(parkEquipmentOnline)
                .parkEquipmentOffline(parkEquipmentOffline)
                .accessControlOnline(accessControlOnline)
                .accessControlOffline(accessControlOffline)
                .incomeAnalysisVoList(new ArrayList<>(incomeAnalysisMap.values()))
                .typeAnalysisVoList(new ArrayList<>(typeAnalysisMap.values()))
                .passengerAnalysisVoList(new ArrayList<>(passengerAnalysisMap.values()))
                .build();
    }

    private void handlePassengerAnalysisList(List<PassengerAnalysisVo> passengerAnalysisVoList, Map<String, PassengerAnalysisVo> passengerAnalysisMap) {
        passengerAnalysisVoList.forEach(passengerNew -> {
            String yearMonth = passengerNew.getYearMonth();
            PassengerAnalysisVo passengerOld = passengerAnalysisMap.get(yearMonth);
            if (null == passengerOld) {
                passengerAnalysisMap.put(yearMonth, passengerNew);
            } else {
                passengerOld.setUserCurrent(passengerOld.getUserCurrent() + passengerNew.getUserCurrent());
                passengerOld.setVisitorCurrent(passengerOld.getVisitorCurrent() + passengerNew.getVisitorCurrent());
                passengerAnalysisMap.put(yearMonth, passengerOld);
            }
        });
    }

    private void handleTypeAnalysisList(List<TypeAnalysisVo> typeAnalysisVoList, Map<String, TypeAnalysisVo> typeAnalysisMap) {
        typeAnalysisVoList.forEach(typeNew -> {
            String yearMonth = typeNew.getYearMonth();
            TypeAnalysisVo typeOld = typeAnalysisMap.get(yearMonth);
            if (null == typeOld) {
                typeAnalysisMap.put(yearMonth, typeNew);
            } else {
                typeOld.setMonthCashPayNum(typeOld.getMonthCashPayNum() + typeNew.getMonthCashPayNum());
                typeOld.setMonthElectronicPayNum(typeOld.getMonthElectronicPayNum() + typeNew.getMonthElectronicPayNum());
                typeOld.setMonthEtcPayNum(typeOld.getMonthEtcPayNum() + typeNew.getMonthEtcPayNum());
                typeOld.setMonthBalancePayNum(typeOld.getMonthBalancePayNum() + typeNew.getMonthBalancePayNum());

                typeOld.setBalanceCashPayNum(typeOld.getBalanceCashPayNum() + typeNew.getBalanceCashPayNum());
                typeOld.setBalanceElectronicPayNum(typeOld.getBalanceElectronicPayNum() + typeNew.getBalanceElectronicPayNum());
                typeOld.setBalanceEtcPayNum(typeOld.getBalanceEtcPayNum() + typeNew.getBalanceEtcPayNum());
                typeOld.setBalanceBalancePayNum(typeOld.getBalanceBalancePayNum() + typeNew.getBalanceBalancePayNum());

                typeOld.setTempCashPayNum(typeOld.getTempCashPayNum() + typeNew.getTempCashPayNum());
                typeOld.setTempElectronicPayNum(typeOld.getTempElectronicPayNum() + typeNew.getTempElectronicPayNum());
                typeOld.setTempEtcPayNum(typeOld.getTempEtcPayNum() + typeNew.getTempEtcPayNum());
                typeOld.setTempBalancePayNum(typeOld.getTempBalancePayNum() + typeNew.getTempBalancePayNum());

                typeOld.setBusinessCashPayNum(typeOld.getBusinessCashPayNum() + typeNew.getBusinessCashPayNum());
                typeOld.setBusinessElectronicPayNum(typeOld.getBusinessElectronicPayNum() + typeNew.getBusinessElectronicPayNum());
                typeOld.setBusinessEtcPayNum(typeOld.getBusinessEtcPayNum() + typeNew.getBusinessEtcPayNum());
                typeOld.setBusinessBalancePayNum(typeOld.getBusinessBalancePayNum() + typeNew.getBusinessBalancePayNum());

                typeAnalysisMap.put(yearMonth, typeOld);
            }
        });
    }

    private void handleIncomeAnalysisList(List<IncomeAnalysisVo> incomeAnalysisVoList, Map<String, IncomeAnalysisVo> incomeAnalysisMap) {
        incomeAnalysisVoList.forEach(incomeNew -> {
            String yearMonth = incomeNew.getYearMonth();
            IncomeAnalysisVo incomeOld = incomeAnalysisMap.get(yearMonth);
            if (null == incomeOld) {
                incomeAnalysisMap.put(yearMonth, incomeNew);
            } else {
                incomeOld.setMonthlyCardPrice(incomeOld.getMonthlyCardPrice().add(incomeNew.getMonthlyCardPrice()));
                incomeOld.setRechargePrice(incomeOld.getRechargePrice().add(incomeNew.getRechargePrice()));
                incomeOld.setTemporaryStopPrice(incomeOld.getTemporaryStopPrice().add(incomeNew.getTemporaryStopPrice()));
                incomeAnalysisMap.put(yearMonth, incomeOld);
            }
        });
    }

    private IndexVo statisticsByPark(Long parkId, Date starDate, Date endDate, Date lastStartDate, Date lastEndDate) {
        // 根据项目ID统计车场某段时间内的数据
        IndexVo currentIndexVo = doBaseStatisticsByPark(parkId, starDate, endDate);
        IndexVo lastIndexVo = doBaseStatisticsByPark(parkId, lastStartDate, lastEndDate);
        // 计算环比
        computePerCent(currentIndexVo, lastIndexVo);
        // 人流量
        doGetVerifyRecordsByPark(parkId, starDate, endDate, currentIndexVo);
        // 设备状态
        doGetEquipmentStatusByPark(parkId, currentIndexVo);
        // 收入分析
        doGetIncomeAnalysisByPark(parkId, currentIndexVo);
        // 支付类型分析
        doGetTypeAnalysisByPark(parkId, currentIndexVo);
        // 人流量分析
        doGetPassengerAnalysisByPark(parkId, currentIndexVo);

        return currentIndexVo;
    }

    /**
     * 根据项目ID统计车场某段时间内的数据
     */
    private IndexVo doBaseStatisticsByPark(Long parkId, Date starDate, Date endDate) {
        PayOrderDto payOrderDto = payOrderService.getRevenue(parkId, PAID_LIST, starDate, endDate);
        // 总收入
        long totalRevenue = payOrderDto.getTotalMoney().longValue();
        // 临停缴费
        long temporaryRevenue = payOrderDto.getTemporaryStopPrice().longValue();
        // 月卡缴费
        long monthlyCardRevenue = payOrderDto.getMonthlyCardPrice().longValue();
        // 商家充值
        long businessRevenue = 0;

        // 进场车辆
        long inCarRevenue = parkInService.getInCarRevenue(parkId, starDate, endDate);
        // 出场车辆
        long outCarRevenue = parkOutService.getOutCarRevenue(parkId, starDate, endDate);

        return IndexVo.builder()
                .totalRevenue(totalRevenue)
                .temporaryRevenue(temporaryRevenue)
                .monthlyCardRevenue(monthlyCardRevenue)
                .businessRevenue(businessRevenue)
                .inCarRevenue(inCarRevenue)
                .outCarRevenue(outCarRevenue)
                .build();
    }

    /**
     * 计算环比
     */
    private void computePerCent(IndexVo currentIndexVo, IndexVo lastIndexVo) {
        // 总收入环比
        double totalRingThan = (BigDecimal.valueOf(currentIndexVo.getTotalRevenue()).subtract(BigDecimal.valueOf(lastIndexVo.getTotalRevenue())))
                .divide(BigDecimal.valueOf(lastIndexVo.getTotalRevenue() == 0 ? 1 : lastIndexVo.getTotalRevenue()), 4, RoundingMode.UP)
                .multiply(HUNDRED).doubleValue();
        currentIndexVo.setTotalRingThan(totalRingThan);

        // 临时停车收入环比
        double temporaryRingThan = (BigDecimal.valueOf(currentIndexVo.getTemporaryRevenue()).subtract(BigDecimal.valueOf(lastIndexVo.getTemporaryRevenue())))
                .divide(BigDecimal.valueOf(lastIndexVo.getTemporaryRevenue() == 0 ? 1 : lastIndexVo.getTemporaryRevenue()), 4, RoundingMode.UP)
                .multiply(HUNDRED).doubleValue();
        currentIndexVo.setTemporaryRingThan(temporaryRingThan);

        // 月卡缴费环比
        double monthlyCardRingThan = (BigDecimal.valueOf(currentIndexVo.getMonthlyCardRevenue()).subtract(BigDecimal.valueOf(lastIndexVo.getMonthlyCardRevenue())))
                .divide(BigDecimal.valueOf(lastIndexVo.getMonthlyCardRevenue() == 0 ? 1 : lastIndexVo.getMonthlyCardRevenue()), 4, RoundingMode.UP)
                .multiply(HUNDRED).doubleValue();
        currentIndexVo.setMonthlyCardRingThan(monthlyCardRingThan);

        // 商家充值缴费环比
        double businessRingThan = (BigDecimal.valueOf(currentIndexVo.getBusinessRevenue()).subtract(BigDecimal.valueOf(lastIndexVo.getBusinessRevenue())))
                .divide(BigDecimal.valueOf(lastIndexVo.getBusinessRevenue() == 0 ? 1 : lastIndexVo.getBusinessRevenue()), 4, RoundingMode.UP)
                .multiply(HUNDRED).doubleValue();
        currentIndexVo.setBusinessRingThan(businessRingThan);

        // 车辆进场环比
        double inCarRingThan = (BigDecimal.valueOf(currentIndexVo.getInCarRevenue()).subtract(BigDecimal.valueOf(lastIndexVo.getInCarRevenue())))
                .divide(BigDecimal.valueOf(lastIndexVo.getInCarRevenue() == 0 ? 1 : lastIndexVo.getInCarRevenue()), 4, RoundingMode.UP)
                .multiply(HUNDRED).doubleValue();
        currentIndexVo.setInCarRingThan(inCarRingThan);

        // 车辆出场环比
        double outCarRingThan = (BigDecimal.valueOf(currentIndexVo.getOutCarRevenue()).subtract(BigDecimal.valueOf(lastIndexVo.getOutCarRevenue())))
                .divide(BigDecimal.valueOf(lastIndexVo.getOutCarRevenue() == 0 ? 1 : lastIndexVo.getOutCarRevenue()), 4, RoundingMode.UP)
                .multiply(HUNDRED).doubleValue();
        currentIndexVo.setOutCarRingThan(outCarRingThan);
    }

    /**
     * 根据项目ID统计人流量
     */
    private void doGetVerifyRecordsByPark(Long parkId, Date starDate, Date endDate, IndexVo indexVo) {
        VerifyRecords verifyRecords = verifyRecordsService.getCurrent(parkId, starDate, endDate);
        // 住户通行次数
        long userCurrent = verifyRecords.getUserCurrent();
        // 访客通行次数
        long visitorCurrent = verifyRecords.getVisitorCurrent();

        indexVo.setUserCurrent(userCurrent);
        indexVo.setVisitorCurrent(visitorCurrent);
    }

    /**
     * 根据项目ID统计设备状态
     */
    private void doGetEquipmentStatusByPark(Long parkId, IndexVo indexVo) {
        ParkDeviceStatisticsDto parkDeviceStatisticsDto = parkDeviceNewService.selectOnLineStatus(parkId);
        // 车场设备在线状态
        int parkEquipmentOnline = parkDeviceStatisticsDto.getOnlineNum();

        // 车场设备离线状态
        int parkEquipmentOffline = parkDeviceStatisticsDto.getNoOnlineNum();

        // 门禁设备在线状态
        int accessControlOnline = accessDeviceService.getAccessDeviceOnline(parkId);

        // 门禁设备离线状态
        int accessControlOffline = accessDeviceService.getAccessDeviceOffline(parkId);

        indexVo.setParkEquipmentOnline(parkEquipmentOnline);
        indexVo.setParkEquipmentOffline(parkEquipmentOffline);
        indexVo.setAccessControlOnline(accessControlOnline);
        indexVo.setAccessControlOffline(accessControlOffline);
    }

    /**
     * 根据项目ID统计收入分析
     */
    private void doGetIncomeAnalysisByPark(Long parkId, IndexVo indexVo) {
        // 获取当前年月
        int year = DateUtils.getYears();
        int month = DateUtils.getMonth();
        int intervalMonth = 11;
        int startMonth = month - intervalMonth;
        List<PayOrderDto> payOrderDtoList = payOrderService.selectParkingOrderCountByMonth(year, startMonth, month, parkId, PAID_LIST);
        List<IncomeAnalysisVo> incomeAnalysisVoList = new ArrayList<>();
        payOrderDtoList.forEach(payOrderDto -> {
            // 年月
            String yearMonth = payOrderDto.getYearMonth();
            // 月卡付费总金额
            BigDecimal monthlyCardPrice = payOrderDto.getMonthlyCardPrice();
            // 储值充值总金额
            BigDecimal rechargePrice = payOrderDto.getRechargePrice();
            // 临停收费总金额
            BigDecimal temporaryStopPrice = payOrderDto.getTemporaryStopPrice();

            IncomeAnalysisVo incomeAnalysisVo = IncomeAnalysisVo.builder()
                    .yearMonth(yearMonth)
                    .monthlyCardPrice(monthlyCardPrice)
                    .rechargePrice(rechargePrice)
                    .temporaryStopPrice(temporaryStopPrice)
                    .build();

            incomeAnalysisVoList.add(incomeAnalysisVo);
        });

        indexVo.setIncomeAnalysisVoList(incomeAnalysisVoList);
    }

    /**
     * 根据项目ID统计支付类型分析
     */
    private void doGetTypeAnalysisByPark(Long parkId, IndexVo indexVo) {
        // 获取当前年月
        int year = DateUtils.getYears();
        int month = DateUtils.getMonth();
        int intervalMonth = 11;
        int startMonth = month - intervalMonth;
        List<PayOrderDto> payOrderDtoList = payOrderService.selectPayTypeCountByMonth(year, startMonth, month, parkId);
        List<TypeAnalysisVo> typeAnalysisVoList = new ArrayList<>();
        payOrderDtoList.forEach(payOrderDto -> {
            TypeAnalysisVo typeAnalysisVo = TypeAnalysisVo.builder()
                    .yearMonth(payOrderDto.getYearMonth())
                    .monthCashPayNum(payOrderDto.getMonthCashPayNum())
                    .monthElectronicPayNum(payOrderDto.getMonthElectronicPayNum())
                    .monthEtcPayNum(payOrderDto.getMonthEtcPayNum())
                    .monthBalancePayNum(payOrderDto.getMonthBalancePayNum())
                    .balanceCashPayNum(payOrderDto.getBalanceCashPayNum())
                    .balanceElectronicPayNum(payOrderDto.getBalanceElectronicPayNum())
                    .balanceEtcPayNum(payOrderDto.getBalanceEtcPayNum())
                    .balanceBalancePayNum(payOrderDto.getBalanceBalancePayNum())
                    .tempCashPayNum(payOrderDto.getTempCashPayNum())
                    .tempElectronicPayNum(payOrderDto.getTempElectronicPayNum())
                    .tempEtcPayNum(payOrderDto.getTempEtcPayNum())
                    .tempBalancePayNum(payOrderDto.getTempBalancePayNum())
                    .businessCashPayNum(payOrderDto.getBusinessCashPayNum())
                    .businessElectronicPayNum(payOrderDto.getBusinessElectronicPayNum())
                    .businessEtcPayNum(payOrderDto.getBusinessEtcPayNum())
                    .businessBalancePayNum(payOrderDto.getBusinessBalancePayNum())
                    .build();
            typeAnalysisVoList.add(typeAnalysisVo);
        });

        indexVo.setTypeAnalysisVoList(typeAnalysisVoList);
    }

    /**
     * 根据项目ID统计人流量分析
     */
    private void doGetPassengerAnalysisByPark(Long parkId, IndexVo indexVo) {
        // 获取当前年月
        int year = DateUtils.getYears();
        int month = DateUtils.getMonth();
        int intervalMonth = 11;
        int startMonth = month - intervalMonth;
        List<VerifyRecords> verifyRecordsList = verifyRecordsService.selectCountByMonth(year, startMonth, month, parkId);

        List<PassengerAnalysisVo> passengerAnalysisVoList = new ArrayList<>();
        verifyRecordsList.forEach(verifyRecords -> {
            // 年月
            String yearMonth = verifyRecords.getTime();
            // 住户通行次数
            long userCurrent = verifyRecords.getUserCurrent();
            // 访客通行次数
            long visitorCurrent = verifyRecords.getVisitorCurrent();

            PassengerAnalysisVo passengerAnalysisVo = PassengerAnalysisVo.builder()
                    .yearMonth(yearMonth)
                    .userCurrent(userCurrent)
                    .visitorCurrent(visitorCurrent)
                    .build();

            passengerAnalysisVoList.add(passengerAnalysisVo);
        });

        indexVo.setPassengerAnalysisVoList(passengerAnalysisVoList);
    }

    /**
     * 更新缓存
     */
    @Override
    public void updateStatisticsCache() {
        // 获取所有的项目
        List<ParkInfo> parkInfoList = parkInfoService.selectAll();
        parkInfoList.forEach(parkInfo -> {
            long parkId = parkInfo.getId();
            // 更新缓存
            updateCacheByDay(parkId);
            updateCacheByWeek(parkId);
            updateCacheByMonth(parkId);
        });
    }

    /**
     * 更新日缓存
     */
    private void updateCacheByDay(Long parkId) {
        Integer dayType = 1;
        Day day = handleDay(dayType, false);
        IndexVo indexVo = statisticsByPark(parkId, day.getStarDate(), day.getEndDate(),
                day.getLastStartDate(), day.getLastEndDate());

        // index:statistics:parkId:day
        String key = StrUtil.format(CACHE_KEY_DAY, parkId);
        redisUtil.set(key, indexVo);
    }

    /**
     * 更新周缓存
     */
    private void updateCacheByWeek(Long parkId) {
        Integer dayType = 2;
        Day day = handleDay(dayType, false);
        IndexVo indexVo = statisticsByPark(parkId, day.getStarDate(), day.getEndDate(),
                day.getLastStartDate(), day.getLastEndDate());

        // index:statistics:parkId:week
        String key = StrUtil.format(CACHE_KEY_WEEK, parkId);
        redisUtil.set(key, indexVo);
    }

    /**
     * 更新月缓存
     */
    private void updateCacheByMonth(Long parkId) {
        Integer dayType = 3;
        Day day = handleDay(dayType, false);
        IndexVo indexVo = statisticsByPark(parkId, day.getStarDate(), day.getEndDate(),
                day.getLastStartDate(), day.getLastEndDate());

        // index:statistics:parkId:month
        String key = StrUtil.format(CACHE_KEY_MONTH, parkId);
        redisUtil.set(key, indexVo);
    }

    /**
     * 查找服务商名下所有下级服务商
     *
     * @param deptId 顶级服务商ID
     * @return 服务商ID列表
     */
    private List<Long> findDeptList(Long deptId) {
        // 获取所有的服务商
        R<List<SystemDeptDTO>> systemDeptResult = remoteSystemDeptService.allList();
        List<SystemDeptDTO> systemDeptList = systemDeptResult.getData();

        // 服务商名下的所有子服务商，包括自己
        List<Long> deptIdList = new ArrayList<>();
        deptIdList.add(deptId);

        findChildDept(deptId, systemDeptList, deptIdList);

        return deptIdList;
    }

    private void findChildDept(long deptId, List<SystemDeptDTO> systemDeptList, List<Long> deptIdList) {
        systemDeptList.forEach(systemDeptDTO -> {
            long parentId = systemDeptDTO.getParentId();
            if (deptId == parentId) {
                long childDeptId = systemDeptDTO.getDeptId();
                deptIdList.add(childDeptId);
                findChildDept(childDeptId, systemDeptList, deptIdList);
            }
        });
    }

    /**
     * 判断登录用户
     */
    private LoginSystemUser getLoginSysUser() {
        LoginSystemUser loginSystemUser = tokenService.getLoginUser();
        if (loginSystemUser == null) {
            throw new IllegalArgumentException(LOGIN_USER_GET_ERROR);
        }
        return loginSystemUser;
    }

    /**
     * 处理统计日期
     */
    private Day handleDay(Integer dayType, boolean isToday) {
        Date date;
        if (isToday) {
            date = new Date();
        } else {
            date = DateUtil.yesterday();
        }

        Date today = new Date();
        Date starDate;
        Date endDate;
        Date lastStartDate;
        Date lastEndDate;

        switch (dayType) {
            // 今天 / 昨天
            case 1:
                // 今天开始结束时间 / 昨天开始结束时间
                starDate = DateUtil.beginOfDay(date);
                endDate = DateUtil.endOfDay(date);
                // 昨天开始结束时间 / 前天开始结束时间
                Date lastDate = DateUtil.offsetDay(date, -1);
                lastStartDate = DateUtil.beginOfDay(lastDate);
                lastEndDate = DateUtil.endOfDay(lastDate);
                break;
            // 本周
            case 2:
                // 本周开始结束时间
                starDate = DateUtil.beginOfWeek(today);
                endDate = DateUtil.endOfWeek(today);
                // 上周开始结束时间
                Date lastWeek = DateUtil.lastWeek();
                lastStartDate = DateUtil.beginOfWeek(lastWeek);
                lastEndDate = DateUtil.endOfWeek(lastWeek);
                break;
            // 本月
            case 3:
                // 本月开始结束时间
                starDate = DateUtil.beginOfMonth(today);
                endDate = DateUtil.endOfMonth(today);
                // 上月开始结束时间
                Date lastMonth = DateUtil.lastMonth();
                lastStartDate = DateUtil.beginOfMonth(lastMonth);
                lastEndDate = DateUtil.endOfMonth(lastMonth);
                break;
            default:
                throw new IllegalArgumentException(DAY_TYPE_ERROR);
        }

        return Day.builder()
                .starDate(starDate)
                .endDate(endDate)
                .lastStartDate(lastStartDate)
                .lastEndDate(lastEndDate)
                .build();
    }

    @Builder
    @Setter
    @Getter
    private static class Day {

        /**
         * 开始日期
         */
        private Date starDate;

        /**
         * 结束日期
         */
        private Date endDate;

        /**
         * 上次开始日期 （环比计算用）
         */
        private Date lastStartDate;

        /**
         * 上次结束日期 （环比计算用）
         */
        private Date lastEndDate;

    }

}
